﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;

namespace Wrox.Metro.Common
{
  /// <summary>
  /// Wrapper for <see cref="RichTextBlock"/> that creates as many additional overflow
  /// columns as needed to fit the available content.
  /// </summary>
  /// <example>
  /// The following creates a collection of 400-pixel wide columns spaced 50 pixels apart
  /// to contain arbitrary data-bound content:
  /// <code>
  /// <RichTextColumns>
  ///     <RichTextColumns.ColumnTemplate>
  ///         <DataTemplate>
  ///             <RichTextBlockOverflow Width="400" Margin="50,0,0,0"/>
  ///         </DataTemplate>
  ///     </RichTextColumns.ColumnTemplate>
  ///     
  ///     <RichTextBlock Width="400">
  ///         <Paragraph>
  ///             <Run Text="{Binding Content}"/>
  ///         </Paragraph>
  ///     </RichTextBlock>
  /// </RichTextColumns>
  /// </code>
  /// </example>
  /// <remarks>Typically used in a horizontally scrolling region where an unbounded amount of
  /// space allows for all needed columns to be created.  When used in a vertically scrolling
  /// space there will never be any additional columns.</remarks>
  [Windows.UI.Xaml.Markup.ContentProperty(Name = "RichTextContent")]
  public sealed class RichTextColumns : Panel
  {
    /// <summary>
    /// Identifies the <see cref="RichTextContent"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty RichTextContentProperty =
        DependencyProperty.Register("RichTextContent", typeof(RichTextBlock),
        typeof(RichTextColumns), new PropertyMetadata(null, ResetOverflowLayout));

    /// <summary>
    /// Identifies the <see cref="ColumnTemplate"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ColumnTemplateProperty =
        DependencyProperty.Register("ColumnTemplate", typeof(DataTemplate),
        typeof(RichTextColumns), new PropertyMetadata(null, ResetOverflowLayout));

    /// <summary>
    /// Initializes a new instance of the <see cref="RichTextColumns"/> class.
    /// </summary>
    public RichTextColumns()
    {
      this.HorizontalAlignment = HorizontalAlignment.Left;
    }

    /// <summary>
    /// Gets or sets the initial rich text content to be used as the first column.
    /// </summary>
    public RichTextBlock RichTextContent
    {
      get { return (RichTextBlock)GetValue(RichTextContentProperty); }
      set { SetValue(RichTextContentProperty, value); }
    }

    /// <summary>
    /// Gets or sets the template used to create additional
    /// <see cref="RichTextBlockOverflow"/> instances.
    /// </summary>
    public DataTemplate ColumnTemplate
    {
      get { return (DataTemplate)GetValue(ColumnTemplateProperty); }
      set { SetValue(ColumnTemplateProperty, value); }
    }

    /// <summary>
    /// Invoked when the content or overflow template is changed to recreate the column layout.
    /// </summary>
    /// <param name="d">Instance of <see cref="RichTextColumns"/> where the change
    /// occurred.</param>
    /// <param name="e">Event data describing the specific change.</param>
    private static void ResetOverflowLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      // When dramatic changes occur, rebuild the column layout from scratch
      var target = d as RichTextColumns;
      if (target != null)
      {
        target._overflowColumns = null;
        target.Children.Clear();
        target.InvalidateMeasure();
      }
    }

    /// <summary>
    /// Lists overflow columns already created.  Must maintain a 1:1 relationship with
    /// instances in the <see cref="Panel.Children"/> collection following the initial
    /// RichTextBlock child.
    /// </summary>
    private List<RichTextBlockOverflow> _overflowColumns = null;

    /// <summary>
    /// Determines whether additional overflow columns are needed and if existing columns can
    /// be removed.
    /// </summary>
    /// <param name="availableSize">The size of the space available, used to constrain the
    /// number of additional columns that can be created.</param>
    /// <returns>The resulting size of the original content plus any extra columns.</returns>
    protected override Size MeasureOverride(Size availableSize)
    {
      if (this.RichTextContent == null) return new Size(0, 0);

      // Make sure the RichTextBlock is a child, using the lack of
      // a list of additional columns as a sign that this hasn't been
      // done yet
      if (this._overflowColumns == null)
      {
        Children.Add(this.RichTextContent);
        this._overflowColumns = new List<RichTextBlockOverflow>();
      }

      // Start by measuring the original RichTextBlock content
      this.RichTextContent.Measure(availableSize);
      var maxWidth = this.RichTextContent.DesiredSize.Width;
      var maxHeight = this.RichTextContent.DesiredSize.Height;
      var hasOverflow = this.RichTextContent.HasOverflowContent;

      // Make sure there are enough overflow columns
      int overflowIndex = 0;
      while (hasOverflow && maxWidth < availableSize.Width && this.ColumnTemplate != null)
      {
        // Use existing overflow columns until we run out, then create
        // more from the supplied template
        RichTextBlockOverflow overflow;
        if (this._overflowColumns.Count > overflowIndex)
        {
          overflow = this._overflowColumns[overflowIndex];
        }
        else
        {
          overflow = (RichTextBlockOverflow)this.ColumnTemplate.LoadContent();
          this._overflowColumns.Add(overflow);
          this.Children.Add(overflow);
          if (overflowIndex == 0)
          {
            this.RichTextContent.OverflowContentTarget = overflow;
          }
          else
          {
            this._overflowColumns[overflowIndex - 1].OverflowContentTarget = overflow;
          }
        }

        // Measure the new column and prepare to repeat as necessary
        overflow.Measure(new Size(availableSize.Width - maxWidth, availableSize.Height));
        maxWidth += overflow.DesiredSize.Width;
        maxHeight = Math.Max(maxHeight, overflow.DesiredSize.Height);
        hasOverflow = overflow.HasOverflowContent;
        overflowIndex++;
      }

      // Disconnect extra columns from the overflow chain, remove them from our private list
      // of columns, and remove them as children
      if (this._overflowColumns.Count > overflowIndex)
      {
        if (overflowIndex == 0)
        {
          this.RichTextContent.OverflowContentTarget = null;
        }
        else
        {
          this._overflowColumns[overflowIndex - 1].OverflowContentTarget = null;
        }
        while (this._overflowColumns.Count > overflowIndex)
        {
          this._overflowColumns.RemoveAt(overflowIndex);
          this.Children.RemoveAt(overflowIndex + 1);
        }
      }

      // Report final determined size
      return new Size(maxWidth, maxHeight);
    }

    /// <summary>
    /// Arranges the original content and all extra columns.
    /// </summary>
    /// <param name="finalSize">Defines the size of the area the children must be arranged
    /// within.</param>
    /// <returns>The size of the area the children actually required.</returns>
    protected override Size ArrangeOverride(Size finalSize)
    {
      double maxWidth = 0;
      double maxHeight = 0;
      foreach (var child in Children)
      {
        child.Arrange(new Rect(maxWidth, 0, child.DesiredSize.Width, finalSize.Height));
        maxWidth += child.DesiredSize.Width;
        maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
      }
      return new Size(maxWidth, maxHeight);
    }
  }
}
